﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;

namespace Microsoft.WowAddonStudio.Lua.Model
{
    /// <summary>
    /// A Lua table that can hold any number of declarations as its fields.
    /// </summary>
    /// <remarks>
    /// A Lua <see cref="Table"/> can be marked virtual in which case the table is unavailable
    /// for callers but can be used as the <see cref="Table.Prototype"/> of another table.
    /// </remarks>
    public class Table : Declaration
    {
        private List<Declaration> declarations;

        /// <summary>
        /// Gets the type of the table.
        /// </summary>
        [XmlAttribute("type")]
        [DefaultValue(LuaType.Table)]
        public override LuaType Type
        {
            get { return LuaType.Table; }
            set
            {
                if (value != LuaType.Table)
                    throw new InvalidOperationException("The type is not allowed to be set to other than Table.");
            }
        }

        /// <summary>
        /// Gets or sets the field declarations.
        /// </summary>
        /// <remarks>
        /// The <see cref="Element.Name"/> of the <see cref="Element"/>s act as the names of the
        /// fields in the table.
        /// </remarks>
        [XmlElement("function", typeof(Function))]
        [XmlElement("table", typeof(Table))]
        [XmlElement("method", typeof(Method))]
        [XmlElement("variable", typeof(Variable))]
        public List<Declaration> Declarations
        {
            get
            {
                if (declarations == null)
                    declarations = new List<Declaration>();

                return declarations;
            }
            set { declarations = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the table is virtual.
        /// </summary>
        [XmlAttribute("virtual")]
        [DefaultValue(false)]
        public bool Virtual { get; set; }

        /// <summary>
        /// Gets or sets the name of the table to use as a prototype.
        /// </summary>
        [XmlAttribute("prototype")]
        public string Prototype { get; set; }

        /// <summary>
        /// Gets a value indicating whether the table has a prototype table.
        /// </summary>
        [XmlIgnore]
        public bool HasPrototype
        {
            get { return !String.IsNullOrEmpty(this.Prototype); }
        }

        /// <summary>
        /// Gets a value indicating whether the prototype table has already been merged.
        /// </summary>
        [XmlIgnore]
        public bool IsPrototypeMerged { get; private set; }

        /// <summary>
        /// Resolve a declaration with the given name.
        /// </summary>
        /// <typeparam name="TDeclaration">The type of the declaration.</typeparam>
        /// <param name="name">The name of the declaration.</param>
        /// <returns>An instance of the <typeparamref name="TDeclaration"/> class if it was found; null otherwise.</returns>
        public TDeclaration Resolve<TDeclaration>(QualifiedName name)  where TDeclaration : Declaration
        {
            if (name == null)
                throw new ArgumentNullException("name");

            if (name.IsQualified)
            {
                var qualifiedTable = this.Declarations.OfType<Table>().FirstOrDefault(declaration => declaration.Name == name.FirstIdentifier);
                if (qualifiedTable != null)
                    return qualifiedTable.Resolve<TDeclaration>(name.Unqualify());

                return null;
            }

            return this.Declarations.OfType<TDeclaration>().FirstOrDefault(declaration => declaration.Name == name);
        }

        /// <summary>
        /// Merges another table into the table.
        /// </summary>
        /// <param name="table">The table to merge.</param>
        /// <remarks>Field declarations with the same name are kept.</remarks>
        public void Merge(Table table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            foreach (var field in table.Declarations)
            {
                if (String.IsNullOrEmpty(field.Name) || field.Type == LuaType.Unknown)
                    continue;

                var existingField = this.Declarations.FirstOrDefault(f => f.Name.Equals(field.Name, StringComparison.Ordinal));
                if (existingField != null)
                    continue;

                Declarations.Add(field);
            }

            if (table.Name == this.Prototype)
                this.IsPrototypeMerged = true;
        }
    }
}
